House Prices

1 Objetivos

  • Número de variáveis: 81
  • Tipo de variáveis
      • Inteiras ou discretas:
      • Numéricas ou double
      • Categóricas
      • Qualitativas
    • Qualidade dos dados
      • Quantidade de NA’s por variável
  • Criação de novas variáveis, se precisar
  • Transformação das variáveis, se precisar

2 Conjunto de Dados de Treino

# Read table 
df.train <- data.table::fread('../dados/train.csv', 
                              sep=",", 
                              showProgress = FALSE) %>% 
            data.frame(stringsAsFactors = F)

df.train

3 Há quantos tipo de dados ?

lapply(df.train,class) %>%
  unlist %>% as.character() %>% table
## .
## character   integer 
##        43        38

4 Dados do tipo inteiro

df.int <- df.train[,unlist(lapply(df.train,is.integer))]
df.int

4.1 Resumo dos dados

skim_to_wide(df.int)

veja que há valores nulos presente na amostra, minha estratégia será remover variáveis que possuam muitos valores nulos e imputar dados para as variáveis com poucos valores nulos.

Primeiramente farei uma pré-seleção dos dados afim de saber se os dados que possuem valores nulos tem importância em relação a variável SalePrice.

5 O Algoritmo De Boruta

  • Tenta selecionar as variáveis importantes do conjunto de dados que discriminam a variável resposta.
  • Então ele treina um classificador no conjunto de dados.
  • Faz um rank da importância da variável com base em um score.
set.seed(2510)
boruta.int <- Boruta(SalePrice ~ ., data=na.omit(df.int[,-1]), doTrace=2)  # perform Boruta search
cols.int <- names(boruta.int$finalDecision[boruta.int$finalDecision %in% c("Confirmed", "Tentative")])  # collect

5.1 Plot da importância da variável

plot(boruta.int, cex.axis=.7, las=2, xlab="", main="Variable Importance")

Selecionando as variáveis inteiras

df.int <- df.int[,c('Id',"SalePrice",cols.int)]

6 Qualidade dos dados

6.1 Tipo inteiro: % de valores nulos

apply(is.na(df.int),2,
            function(x) round(100*sum(as.numeric(x))/length(x),2)) %>% 
            sort(decreasing = T)
##   LotFrontage   GarageYrBlt    MasVnrArea            Id     SalePrice 
##         17.74          5.55          0.55          0.00          0.00 
##    MSSubClass       LotArea   OverallQual   OverallCond     YearBuilt 
##          0.00          0.00          0.00          0.00          0.00 
##  YearRemodAdd    BsmtFinSF1     BsmtUnfSF   TotalBsmtSF     X1stFlrSF 
##          0.00          0.00          0.00          0.00          0.00 
##     X2ndFlrSF     GrLivArea  BsmtFullBath      FullBath      HalfBath 
##          0.00          0.00          0.00          0.00          0.00 
##  BedroomAbvGr  KitchenAbvGr  TotRmsAbvGrd    Fireplaces    GarageCars 
##          0.00          0.00          0.00          0.00          0.00 
##    GarageArea    WoodDeckSF   OpenPorchSF EnclosedPorch   ScreenPorch 
##          0.00          0.00          0.00          0.00          0.00

De forma geral não há muitos valores nulos no conjunto de variáveis inteiras, porém a variável LotFrontage apresenta cerca de 17.74 % de valores nulos ausentes e como há uma série de outras variáveis mais importantes que esta, decido por remove-la.

Removendo a variável LotFrontage

df.int <- df.int %>% select(-LotFrontage)

6.2 Imputando dados com knn

Criando um modelo com o knn para imputar dados.

preProcess_missingdata_model <- preProcess(df.int[,-c(1,2)], method='knnImpute')
preProcess_missingdata_model
## Created from 1371 samples and 27 variables
## 
## Pre-processing:
##   - centered (27)
##   - ignored (0)
##   - 5 nearest neighbor imputation (27)
##   - scaled (27)

Vamos agora usar esse modelo para prever os valores ausentes df.int

df.int1 <- predict(preProcess_missingdata_model, newdata = df.int[,-c(1,2)] )
anyNA(df.int1)
## [1] FALSE

Todos os valores ausentes foram imputados com sucesso, agora vamos juntar as colunas Id e SalesPrice com df.int1

df.int <- cbind(df.int[,c(1,2)],df.int1)

Convertento dados tipo string para tipo categórico

df.fac <- df.train[,unlist(lapply(df.train,is.character))] %>%
          apply(2,as.factor) %>% 
          data.frame()

6.3 Tipo Categorical: % de valores nulos

apply(is.na(df.fac),2,function(x) round(100*sum(as.numeric(x))/length(x),2)) %>% 
           sort(decreasing = T)
##        PoolQC   MiscFeature         Alley         Fence   FireplaceQu 
##         99.52         96.30         93.77         80.75         47.26 
##    GarageType  GarageFinish    GarageQual    GarageCond  BsmtExposure 
##          5.55          5.55          5.55          5.55          2.60 
##  BsmtFinType2      BsmtQual      BsmtCond  BsmtFinType1    MasVnrType 
##          2.60          2.53          2.53          2.53          0.55 
##    Electrical      MSZoning        Street      LotShape   LandContour 
##          0.07          0.00          0.00          0.00          0.00 
##     Utilities     LotConfig     LandSlope  Neighborhood    Condition1 
##          0.00          0.00          0.00          0.00          0.00 
##    Condition2      BldgType    HouseStyle     RoofStyle      RoofMatl 
##          0.00          0.00          0.00          0.00          0.00 
##   Exterior1st   Exterior2nd     ExterQual     ExterCond    Foundation 
##          0.00          0.00          0.00          0.00          0.00 
##       Heating     HeatingQC    CentralAir   KitchenQual    Functional 
##          0.00          0.00          0.00          0.00          0.00 
##    PavedDrive      SaleType SaleCondition 
##          0.00          0.00          0.00

Veja que as variáveis PoolQC, MiscFeature, Alley, Fence e FireplaceQu não nos deixam outra alternativa senão a remoção delas do conjunto de dados, pois neste caso, a imputação de dados seria um grande problema na propagação do erro considerando as incertezas associadas aos métodos de imputação.

6.4 Selecão de Variável Tipo String:

  • PoolQC
  • MiscFeature
  • Alley
  • Fence
  • FireplaceQu
df.fac <- df.fac %>% 
            select(-PoolQC,-MiscFeature,
                   -Alley,-Fence,-FireplaceQu)
df.fac

6.5 Pré-Seleção das Variáveis Categóricas

Novamente utilizarei o boruta para realizar uma pré-seleção das variáveis

set.seed(2510)
boruta_fac <- Boruta(SalePrice ~ ., data=na.omit(data.frame(SalePrice = df.int[,'SalePrice'],df.fac)), doTrace=2)  
# perform Boruta search

atribuindo a cols.fac o nome das variáveis selecionadas.

cols.fac <- names(boruta_fac$finalDecision[boruta_fac$finalDecision %in% c("Confirmed", "Tentative")])  

6.6 Plot da importância da variável

plot(boruta_fac, cex.axis=.7, las=2, xlab="", main="Variable Importance")

7 Variáveis selecionadas df.fac

df.fac <- df.fac[,cols.fac]
apply(is.na(df.fac),2,function(x) round(100*sum(as.numeric(x))/length(x),2)) %>% 
           sort(decreasing = T)
##    GarageType  GarageFinish    GarageCond  BsmtExposure      BsmtQual 
##          5.55          5.55          5.55          2.60          2.53 
##      BsmtCond  BsmtFinType1    MasVnrType    Electrical      MSZoning 
##          2.53          2.53          0.55          0.07          0.00 
##      LotShape   LandContour     LandSlope  Neighborhood    Condition1 
##          0.00          0.00          0.00          0.00          0.00 
##      BldgType    HouseStyle     RoofStyle      RoofMatl   Exterior1st 
##          0.00          0.00          0.00          0.00          0.00 
##   Exterior2nd     ExterQual     ExterCond    Foundation     HeatingQC 
##          0.00          0.00          0.00          0.00          0.00 
##    CentralAir   KitchenQual    Functional    PavedDrive SaleCondition 
##          0.00          0.00          0.00          0.00          0.00

Como ainda estamos com valores nulos recorremos ao caret para imputar essas categorias.

Para construir um modelo que impute nas variáveis categóricas suas categorias faltantes vamos retirar todas as variáveis que possuem alguma porcentagem de valores nulos e deixar no data frame somente uma delas em cada modelo.

7.1 Criando os data frames

df.GarageType <- df.fac %>%
                    select(-GarageFinish,-GarageCond,
                           -BsmtExposure,-BsmtQual,
                           -BsmtCond,-BsmtFinType1,
                           -MasVnrType,-Electrical)
df.GarageFinish <- df.fac %>% 
                      select(-GarageType,-GarageCond,
                             -BsmtExposure,-BsmtQual,
                             -BsmtCond,-BsmtFinType1,
                             -MasVnrType,-Electrical)
df.GarageCond <- df.fac %>% 
                    select(-GarageType,-GarageFinish,
                           -BsmtExposure,-BsmtQual,
                           -BsmtCond,-BsmtFinType1,
                           -MasVnrType,-Electrical)
df.BsmtExposure <- df.fac %>% 
                      select(-GarageType,-GarageFinish,
                             -GarageCond,-BsmtQual,
                             -BsmtCond,-BsmtFinType1,
                             -MasVnrType,-Electrical)
df.BsmtQual <- df.fac %>% 
                  select(-GarageType,-GarageFinish,
                         -GarageCond,-BsmtExposure,
                         -BsmtCond,-BsmtFinType1,
                         -MasVnrType,-Electrical)
df.BsmtCond <- df.fac %>% 
                  select(-GarageType,-GarageFinish,
                         -GarageCond,-BsmtExposure,
                         -BsmtQual,-BsmtFinType1,
                         -MasVnrType,-Electrical)
df.BsmtFinType1 <- df.fac %>% 
                      select(-GarageType,-GarageFinish,
                             -GarageCond,-BsmtExposure,
                             -BsmtQual,-BsmtCond,
                             -MasVnrType,-Electrical)
df.MasVnrType <- df.fac %>% 
                    select(-GarageType,-GarageFinish,
                           -GarageCond,-BsmtExposure,
                           -BsmtQual,-BsmtCond,
                           -BsmtFinType1,-Electrical)
df.Electrical <- df.fac %>% 
                  select(-GarageType,-GarageFinish,
                         -GarageCond,-BsmtExposure,
                         -BsmtQual,-BsmtCond,
                         -BsmtFinType1,-MasVnrType)

criando um vetor com o nome das variáveis e uma lista com os data-frames criados.

vars <- c('GarageType','GarageFinish',
          'GarageCond','BsmtExposure',
          'BsmtQual','BsmtCond',
          'BsmtFinType1','MasVnrType',
          'Electrical')

list.df <- list(df.GarageType,df.GarageFinish,
                df.GarageCond,df.BsmtExposure,
                df.BsmtQual,df.BsmtCond,
                df.BsmtFinType1,df.MasVnrType,
                df.Electrical)

A função abaixo automatiza o processo de impute das categoricas nos valores nulos de cada variável.

f.pred <- function(fac,df.var,rf.model,var){ 

  new.df <- df.var[is.na(df.var[,var]),!(names(df.var) %in% var)]
  pred_rf <- predict(rf.model, newdata =  new.df)
  fac[is.na(fac[,var]),var] <- pred_rf

  return(fac)
}

7.2 Usando a Caret e RandomForest

Utilizarei o random forest como classificador para imputar as categorias faltantes.

set.seed(12345)
fitControl <- trainControl(method="cv", 
                           number=3, 
                           savePredictions = 'final',
                           classProbs= F, 
                           summaryFunction = multiClassSummary)

Construindos os modelos para imputar os valores nulos, para cada variável terei um modelo.

set.seed(12345)

# Crio uma lista para armazenar os modelos
rf.list <- list()

for(j in 1:length(vars)){ 

 # atribuo em df um df."variavel" sem os valor nulos       
 df <- list.df[[j]] %>% na.omit()  

 # treino esse df. no random-forest 
 rf.list[[j]] <- train(eval(parse(text = paste(vars[j],'~.'))),
                                      data = df, 
                                      tuneLength=5,
                                      trControl = fitControl,method='rf')

 # imputo as categorias faltantes no valores nulos das variáveis
 df.fac <- f.pred(df.fac,list.df[[j]],rf.list[[j]],vars[j])
 
 cat(j,' - ')
 
}
## 1  - 2  - 3  - 4  - 5  - 6  - 7  - 8  - 9  -

Será há algum valor nulos ?

anyNA(df.fac)
## [1] FALSE

Imputação de dados realizada com sucesso !

8 Juntandos os data frames

Jutando os dados tipo inteiros e string.

df.train <- bind_cols(df.int,df.fac)

Agora nosso df.train encontra-se limpo e pronto para ser explorado.

9 Exportando os dados

write.csv(df.train,'../outputs/df.train.csv')
saveRDS(cols.int,'../outputs/cols.int.rds')
saveRDS(cols.fac,'../outputs/cols.fac.rds')

Sérgio Carvalho

22 março, 2019